#pragma once
#include "CGIAccess.h"

namespace Alice
{
    class ExclusiveDepthStencil
	{
	public:
		enum Type
		{
			// don't use those directly, use the combined versions below
			// 4 bits are used for depth and 4 for stencil to make the hex value readable and non overlapping
			DepthNop = 0x00,
			DepthRead = 0x01,
			DepthWrite = 0x02,
			DepthMask = 0x0f,
			StencilNop = 0x00,
			StencilRead = 0x10,
			StencilWrite = 0x20,
			StencilMask = 0xf0,

			// use those:
			DepthNop_StencilNop = DepthNop + StencilNop,
			DepthRead_StencilNop = DepthRead + StencilNop,
			DepthWrite_StencilNop = DepthWrite + StencilNop,
			DepthNop_StencilRead = DepthNop + StencilRead,
			DepthRead_StencilRead = DepthRead + StencilRead,
			DepthWrite_StencilRead = DepthWrite + StencilRead,
			DepthNop_StencilWrite = DepthNop + StencilWrite,
			DepthRead_StencilWrite = DepthRead + StencilWrite,
			DepthWrite_StencilWrite = DepthWrite + StencilWrite,
		};

	private:
		Type Value;

	public:
		// constructor
		ExclusiveDepthStencil(Type InValue = DepthNop_StencilNop)
			: Value(InValue)
		{
		}

		inline bool IsUsingDepthStencil() const
		{
			return Value != DepthNop_StencilNop;
		}
		inline bool IsUsingDepth() const
		{
			return (ExtractDepth() != DepthNop);
		}
		inline bool IsUsingStencil() const
		{
			return (ExtractStencil() != StencilNop);
		}
		inline bool IsDepthWrite() const
		{
			return ExtractDepth() == DepthWrite;
		}
		inline bool IsDepthRead() const
		{
			return ExtractDepth() == DepthRead;
		}
		inline bool IsStencilWrite() const
		{
			return ExtractStencil() == StencilWrite;
		}
		inline bool IsStencilRead() const
		{
			return ExtractStencil() == StencilRead;
		}

		inline bool IsAnyWrite() const
		{
			return IsDepthWrite() || IsStencilWrite();
		}

		inline void SetDepthWrite()
		{
			Value = (Type)(ExtractStencil() | DepthWrite);
		}
		inline void SetStencilWrite()
		{
			Value = (Type)(ExtractDepth() | StencilWrite);
		}
		inline void SetDepthStencilWrite(bool bDepth, bool bStencil)
		{
			Value = DepthNop_StencilNop;

			if (bDepth)
			{
				SetDepthWrite();
			}
			if (bStencil)
			{
				SetStencilWrite();
			}
		}
		bool operator==(const ExclusiveDepthStencil& rhs) const
		{
			return Value == rhs.Value;
		}

		bool operator != (const ExclusiveDepthStencil& RHS) const
		{
			return Value != RHS.Value;
		}

		inline bool IsValid(ExclusiveDepthStencil& Current) const
		{
			Type Depth = ExtractDepth();

			if (Depth != DepthNop && Depth != Current.ExtractDepth())
			{
				return false;
			}

			Type Stencil = ExtractStencil();

			if (Stencil != StencilNop && Stencil != Current.ExtractStencil())
			{
				return false;
			}

			return true;
		}

		inline void GetAccess(ECGIAccess& DepthAccess, ECGIAccess& StencilAccess) const
		{
			DepthAccess = ECGIAccess::None;

			// SRV access is allowed whilst a depth stencil target is "readable".
			constexpr ECGIAccess DSVReadOnlyMask =
				ECGIAccess::DSVRead;

			// If write access is required, only the depth block can access the resource.
			constexpr ECGIAccess DSVReadWriteMask =
				ECGIAccess::DSVRead |
				ECGIAccess::DSVWrite;

			if (IsUsingDepth())
			{
				DepthAccess = IsDepthWrite() ? DSVReadWriteMask : DSVReadOnlyMask;
			}

			StencilAccess = ECGIAccess::None;

			if (IsUsingStencil())
			{
				StencilAccess = IsStencilWrite() ? DSVReadWriteMask : DSVReadOnlyMask;
			}
		}

		template <typename TFunction>
		inline void EnumerateSubresources(TFunction Function) const
		{
			if (!IsUsingDepthStencil())
			{
				return;
			}

			ECGIAccess DepthAccess = ECGIAccess::None;
			ECGIAccess StencilAccess = ECGIAccess::None;
			GetAccess(DepthAccess, StencilAccess);

			// Same depth / stencil state; single subresource.
			if (DepthAccess == StencilAccess)
			{
				Function(DepthAccess, FRHITransitionInfo::kAllSubresources);
			}
			// Separate subresources for depth / stencil.
			else
			{
				if (DepthAccess != ECGIAccess::None)
				{
					Function(DepthAccess, FRHITransitionInfo::kDepthPlaneSlice);
				}
				if (StencilAccess != ECGIAccess::None)
				{
					Function(StencilAccess, FRHITransitionInfo::kStencilPlaneSlice);
				}
			}
		}

		/**
		* Returns a new FExclusiveDepthStencil to be used to transition a depth stencil resource to readable.
		* If the depth or stencil is already in a readable state, that particular component is returned as Nop,
		* to avoid unnecessary subresource transitions.
		*/
		inline ExclusiveDepthStencil GetReadableTransition() const
		{
			ExclusiveDepthStencil::Type NewDepthState = IsDepthWrite()
				? ExclusiveDepthStencil::DepthRead
				: ExclusiveDepthStencil::DepthNop;

			ExclusiveDepthStencil::Type NewStencilState = IsStencilWrite()
				? ExclusiveDepthStencil::StencilRead
				: ExclusiveDepthStencil::StencilNop;

			return (ExclusiveDepthStencil::Type)(NewDepthState | NewStencilState);
		}

		/**
		* Returns a new FExclusiveDepthStencil to be used to transition a depth stencil resource to readable.
		* If the depth or stencil is already in a readable state, that particular component is returned as Nop,
		* to avoid unnecessary subresource transitions.
		*/
		inline ExclusiveDepthStencil GetWritableTransition() const
		{
			ExclusiveDepthStencil::Type NewDepthState = IsDepthRead()
				? ExclusiveDepthStencil::DepthWrite
				: ExclusiveDepthStencil::DepthNop;

			ExclusiveDepthStencil::Type NewStencilState = IsStencilRead()
				? ExclusiveDepthStencil::StencilWrite
				: ExclusiveDepthStencil::StencilNop;

			return (ExclusiveDepthStencil::Type)(NewDepthState | NewStencilState);
		}

		uint32 GetIndex() const
		{
			// Note: The array to index has views created in that specific order.

			// we don't care about the Nop versions so less views are needed
			// we combine Nop and Write
			switch (Value)
			{
			case DepthWrite_StencilNop:
			case DepthNop_StencilWrite:
			case DepthWrite_StencilWrite:
			case DepthNop_StencilNop:
				return 0; // old DSAT_Writable

			case DepthRead_StencilNop:
			case DepthRead_StencilWrite:
				return 1; // old DSAT_ReadOnlyDepth

			case DepthNop_StencilRead:
			case DepthWrite_StencilRead:
				return 2; // old DSAT_ReadOnlyStencil

			case DepthRead_StencilRead:
				return 3; // old DSAT_ReadOnlyDepthAndStencil
			}
			// should never happen
			return -1;
		}
		static const uint32 MaxIndex = 4;

	private:
		inline Type ExtractDepth() const
		{
			return (Type)(Value & DepthMask);
		}
		inline Type ExtractStencil() const
		{
			return (Type)(Value & StencilMask);
		}
		friend uint32 GetTypeHash(const ExclusiveDepthStencil& Ds);
	};
}
